<?php
declare(strict_types=1);

namespace lib\pay;

use app\index\BootStarup;
use lib\pay\helpers\PaySign;
use Swoole\Coroutine;
use sys\Log;

/**
 * 会员卡(计费卡)支付
 */
class MemberPay extends PayBase {
    use \lib\data\traits\WaitLock;

    public int $id;
    public int $mid;
    public int $out_mid;
    public string $out_trade_no;
    public string $trade_no;
    public float $amount;
    public int $refund_times;
    public float  $refund_amount;
    public float $finnal_amount;
    public int $order_type;
    public int $order_id;
    public int $stage;
    public string $buyer_id;
    public string $buyer_name;
    public int $datetime;
    public int $expire;

    protected static array $propNamesC = ['id','mid','out_mid','out_trade_no','trade_no','amount','refund_times','refund_amount','finnal_amount','order_type','order_id','stage','buyer_id', 'buyer_name', 'datetime','expire'];
    protected static array $propNamesU = ['refund_times','refund_amount','finnal_amount','order_type','order_id','stage','datetime','expire'];

    protected static array $trade_inprogress = [];
    /**
     * 会员卡扫码支付
     */
    public static function qrcodepay(\lib\Merchant $m, float $total_fee, string $ipaddr, int $timeout, string $body, ?string $attach = null, callable $callback = null) :?static{
        $out_trade_no = \lib\Utils::genOrderNo('Q');
        # 超时时间
        $abs_timeout = time() + $timeout;

        $result = static::call($m, '/member_pay/qrcodepay',[
            'mid'           => $m->id,                     # 网吧商户ID
            'out_mid'       => $m->id,                     # 己方商户ID(商品平台)
            'mode'          => $m->config->memberPay->mode, # 扣款方式: 0: 只扣本金，无本金的情况下不可购买.  1: 本金+赠送 等比扣除 2: 本金+赠送 本金优先
            'total_fee'     => floor($total_fee * 100),
            'ipaddr'        => $ipaddr,
            'timeout'       => $timeout,
            'out_trade_no'  => $out_trade_no,
            'body'          => $body,
            'attach'        => $attach,
            'remark'        => ['mid'=>$m->id], # 己方商户ID
            'notify_url'    =>  'https://goods.80mall.net/callback/mnotify?sid='. BootStarup::$sid,
        ]);

        if($result['success']){
            # 给个二维码.
            static::$qrcodepay_cached[$out_trade_no]  = $channel = new \swoole\Coroutine\Channel(1);
            $callback(['success' => true, 'message'=>'等待用户付款....', 'data' => [
                'timeout'=>$abs_timeout - time(), 
                'out_trade_no'=>$out_trade_no, 
                'qrcode'=>$result['data']['code_url']]]);
            
            # 等待用户付款.
            $pay = $channel->pop($abs_timeout - time());
            unset(static::$qrcodepay_cached[$out_trade_no]);

            if($pay instanceof static){
                $channel->close();
                return $pay;
            }

            if($channel->errCode == SWOOLE_CHANNEL_TIMEOUT){
                unset(static::$qrcodepay_cached[$out_trade_no]);
                if(empty(static::$qrcodepay_cached)){
                    static::$qrcodepay_cached = [];
                }
                $channel->close();
                throw new PayException('等待支付超时.', 1003);
            }
            $channel->close();
            throw new PayException('操作已取消.', 1004);
        }
        throw new PayException($result['message'], 1005);
        
    }
    
    /**
     * 会员卡支付回调
     */
    public static function onNotify(array $params) : bool {
        if($params['success']){
            $trade_no = $params['out_trade_no'];
            if(isset(static::$notify_inprogress[$trade_no])){
                return false;
            }
            static::$notify_inprogress[$trade_no] = true;

            $total_fee = ((int)$params['total_fee']) / 100;
            $tp_nb_goods_pays_member = [
                'mid'             => $params['out_mid'],          # 接口方商户ID
                'out_mid'         => $params['mid'],              # 己方商户ID
                'out_trade_no'    => $params['trade_no'],         # 接口方的交易号
                'trade_no'        => $trade_no,                   # 我们的商户号
                'amount'          => $total_fee,                  # 付款金额
                'refund_times'    => 0,                           # 退款次数
                'refund_amount'   => 0,                           # 退款金额
                'finnal_amount'   => $total_fee,                  # 实际支付金额
                'order_type'      => 256,                         # 未知订单
                'order_id'        => 0,                           # 支付记录关联的订单号
                'stage'           => static::STAGE_PAID,          # 是否完成付款
                'buyer_id'        => $params['buyer_id'] ?? 0,    # 网吧侧的会员ID
                'buyer_name'      => $params['buyer_name'] ?? '', # 网吧侧的会员名称
                'datetime'        => $params['createtime'],       # 交易时间
                'expire'          => 0,
            ];
            $db = new \sys\Db();
            try{
                $tp_nb_goods_pays_member['id'] = $db->table('tp_nb_goods_pays_member')->insert($tp_nb_goods_pays_member);
                $pay = static::createInstance($tp_nb_goods_pays_member);
            }catch(\Throwable $e){
                $err = [
                    'Error:'.$e->getMessage().' at file:'.$e->getFile().'('.$e->getLine().')',
                    "TraceBack:\n".$e->getTraceAsString()
                ];
                Log::write(implode("\n", $err), 'ERROR');
                $pay = null;
            }
            
            Log::console('支付成功: '.$trade_no, 'DEBUG');
            if(null !== ($channel = (static::$qrcodepay_cached[$trade_no] ?? null))){
                $channel->push($pay);
                Log::console('支付成功, 通知商户', 'DEBUG');
                unset(static::$qrcodepay_cached[$trade_no]);
                if(empty(static::$qrcodepay_cached)){
                    static::$qrcodepay_cached = [];
                }

                unset(static::$notify_inprogress[$trade_no]);
                if(empty(static::$notify_inprogress)){
                    static::$notify_inprogress = [];
                }
                return true;
            }
            Log::console('通知商户失败, 开始退款.', 'DEBUG');
            $pay->refund($db, $total_fee, '222.242.224.119', '支付超时自动退款.');
            unset(static::$qrcodepay_cached[$trade_no]);
            if(empty(static::$qrcodepay_cached)){
                static::$qrcodepay_cached = [];
            }
        }
    }

    public function useSql(\sys\Db $db, int $order_type, \sys\db\ExpValue|int $order_id) :\sys\db\SubQuery {
        $this->lock();
        $sql = $db->table('tp_nb_goods_pays_member')->where([['id','=', $this->id]])->subUpdate([
            'order_type'=>$order_type,
            'order_id'=>$order_id
        ]);
        $this->unlock();
        return $sql;
    }

    public function use(int $order_type,int $order_id) : bool {
        $this->lock();
        $this->order_type = $order_type;
        $this->order_id = $order_id;
        $this->unlock();
        return true;
    }

    public function refund(\sys\Db $db, float $amount, string $ipaddr, string $reason) : bool {
        $time = time();
        $this->lock();
        $allow_refund = $this->amount - $this->refund_amount;
        if($allow_refund - $amount < -0.00001){
            $this->unlock();
            throw new PayException("退款金额超过支付金额. {可退: {$allow_refund}, 当前: {$amount}}", 1005);
        }

        if($this->refund_times >= 1295){
            $this->unlock();
            throw new PayException('退款次数超出最大次数限制.', 1005);
        }

        $biz = [
            'ori_out_trade_no'=>$this->out_trade_no,    # 交易单号
            'refund_no'=>\lib\Utils::genOrderNo('R'),   # 退款单号
            'total_fee'=> strval(round(100 * $amount)), # 退款金额
            'mch_create_ip'=>$ipaddr,                   # 请求IP
        ];

        $tp_nb_goods_refund_member = [
            ...$biz, 
            'sid'           => BootStarup::$sid,
            'createtime'    =>  $time,
            'times'         => 0,
            'success'       => 0,   // 0 未开始  1 成功 2 失败 3 出错(不可以再次重试)
        ];

        $tp_nb_goods_refund_member_id = $db->table('tp_nb_goods_refunds_member')->insert($tp_nb_goods_refund_member);
        $merchant = \lib\Merchant::get($this->mid);

        Coroutine::create(function() use($biz, $amount, $merchant, $db,$tp_nb_goods_refund_member_id) {
            $this->take();
            while(true){
                try{
                    $result = static::call($merchant, '/member_pay/refund', $biz);
                    if($result['success']){
                        $db->table('tp_nb_goods_pays_member')
                            ->where(['id','=', $this->id])
                            ->inc('refund_times', 1)
                            ->inc('refund_amount', $amount)
                            ->update(['stage'=> static::STAGE_REFUND]);
                        $db->table('tp_nb_goods_refunds_member')->inc('times', 1)->update(['success'=>1]);
                        $this->unlock();
                        return true;
                    }elseif(isset($result['code'])){
                        $db->table('tp_nb_goods_refunds_member')->where(['id','=',$tp_nb_goods_refund_member_id])->inc('times', 1)->update(['success'=>2, 'message'=>$result['message']]);
                        Log::write("网络错误:5秒后充实重试. {$result['message']}", 'ERROR');
                        Coroutine::sleep(5);
                    }else{
                        $db->table('tp_nb_goods_refunds_member')->where(['id','=',$tp_nb_goods_refund_member_id])->inc('times', 1)->update(['success'=>3, 'message'=>$result['message']]);
                        throw new PayException($result['message'], 1005);
                    }
                }catch(\Throwable $e){
                    $err = [
                        'Error:'.$e->getMessage().' at file:'.$e->getFile().'('.$e->getLine().')',
                        "TraceBack:\n".$e->getTraceAsString()
                    ];
                    Log::write(implode("\n", $err), 'ERROR');
                    $this->unlock();
                    return false;
                }
            }
        });
        return true;
    }

    /**
     * 执行远程调用
     */
    private static function call(\lib\Merchant $merchant, string $api, $biz) :array {
        if($merchant->nbver < 2){
            return ['success'=>false, 'message'=>'商户版本不支持.'];
        }

        $url = $merchant->getPayUrl() . $api;
        $biz['sign'] = PaySign::sign($biz, Pay::SRC_MEMBER);
        $post_str = json_encode($biz, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        

        Log::write("开始向网吧平台发送支付请求:{$url}", 'INFO');
        $return = http_post($url, $post_str, [
            'Content-Type'=>'text/json; charset=utf-8'
        ], 30);

        $ret_str = json_encode($return, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
        Log::write(">>>>>>>>=============================\n{$url}\n=====================================\n{$post_str}\n=====================================\n{$ret_str}\n=====================================\n", 'INFO');

        if(200 == $return['code']){
            $result = json_decode($return['body'], true);
        }else{
            $result = ['success'=>false, 'message'=>'网络错误.', 'code'=>$return['code']];
        }
        return $result;
    }
}